package com.yc.spring.demo;

import com.yc.spring.demo.mapper.UserMapper;
import lombok.Data;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Lazy;
import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Controller;
import org.springframework.stereotype.Repository;
import org.springframework.stereotype.Service;

import java.io.File;
import java.io.UnsupportedEncodingException;
import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * Company 源辰信息
 *
 * @author navy
 * @date 2024/8/1
 * Email haijunzhou@hnit.edu.cn
 */
public class MyAnnotationConfigApplicationContext {

    protected Map<String,BeanDefinition> beanDefinitionMap = new HashMap<>();
    protected Map<String,Object> singletonObjects = new HashMap<>();

    protected MyAnnotationConfigApplicationContext(Class configuration) throws UnsupportedEncodingException, ClassNotFoundException, InvocationTargetException, IllegalAccessException, InstantiationException {
        // 构建 beanDefinition 对象 存入 beanDefinitionMap
        Method[] methods = configuration.getDeclaredMethods();
        Object configurationObject = configuration.newInstance();
        // 1. 解析 bean 方法
        for (Method method : methods) {
            if (method.getAnnotation(Bean.class)!=null) {
                BeanDefinition bd = new BeanDefinition();
                bd.setBeanMethod(method);
                // 保存 configurationObject 对象
                bd.setBeanConfig(configurationObject);
                bd.setBeanClass(method.getReturnType());
                bd.setLazy(method.getAnnotation(Lazy.class)!=null);
                bd.setPrimary(method.getAnnotation(Primary.class)!=null);
                // 获取 bean 的 name
                Bean beanAnno = method.getAnnotation(Bean.class);
                String beanName;
                if (beanAnno.value().length>0) {
                    beanName = beanAnno.value()[0];
                } else {
                    beanName = method.getName();
                }
                beanDefinitionMap.put(beanName,bd);
            }
        }
        // 2. 扫描 bean
        ComponentScan componentScan = (ComponentScan)
                configuration.getAnnotation(ComponentScan.class);
        if(componentScan!=null){
            List<File> classFiles = new ArrayList<>();
            for (String pkg : componentScan.value()) {
                // 使用类加载器读取编译目录,
                // 将 class 文件路径转为 类路径
                // 加载类对象, 读取类上的注解, 判断是否是组件 bean
                String pkgPath = pkg.replaceAll("\\.", "/");
                URL url = getClass().getClassLoader().getResource(pkgPath);
                System.out.println("url = " + url);
                pkgPath = URLDecoder.decode(url.getPath(),"utf-8");
                System.out.println("pkgPath = " + pkgPath);
                File pkgDir = new File(pkgPath);
                scanClassFile(pkgDir, classFiles);
            }
            String rootPath = getClass().getClassLoader().getResource("").getPath();
            rootPath = URLDecoder.decode(rootPath);
            rootPath = new File(rootPath).getAbsolutePath();
            System.out.println("rootPath = " + rootPath);
            for (File classFile : classFiles) {
                String classPath = classFile.getAbsolutePath().replace(rootPath + "\\", "");
                classPath = classPath.replace(".class","");
                classPath = classPath.replaceAll("\\\\",".");
                System.out.println("classPath = " + classPath);
                Class<?> aClass = Class.forName(classPath);

                Service service = aClass.getAnnotation(Service.class);
                Controller controller = aClass.getAnnotation(Controller.class);
                Repository repository = aClass.getAnnotation(Repository.class);
                Component component = aClass.getAnnotation(Component.class);


                if (service!=null||
                        controller!=null||
                        repository!=null||
                        component!=null) {
                    BeanDefinition bd = new BeanDefinition();
                    bd.setBeanClass(aClass);
                    bd.setLazy(aClass.getAnnotation(Lazy.class)!=null);
                    bd.setPrimary(aClass.getAnnotation(Primary.class)!=null);
                    // 构建 bean name
                    // getSimpleName 类名 getName 类路径
                    String name = aClass.getSimpleName();
                    // 默认名: 类名+首字母小写
                    name = name.substring(0,1).toLowerCase() + name.substring(1);
                    if(service!=null && service.value().isBlank()==false){
                        name = service.value();
                    } else if(controller!=null && controller.value().isBlank()==false){
                        name = controller.value();
                    } else if(repository!=null && repository.value().isBlank()==false){
                        name = repository.value();
                    } else if(component!=null && component.value().isBlank()==false){
                        name = component.value();
                    }
                    beanDefinitionMap.put(name,bd);
                }
            }
        }
//        根据 beanDefinition 创建 bean 存入 singletonObjects
        for (Map.Entry<String, BeanDefinition> entry : beanDefinitionMap.entrySet()) {
            String name = entry.getKey();
            BeanDefinition beanDefinition = entry.getValue();
            if (beanDefinition.isLazy()) {
                continue;
            }
            Object bean = null;
            if(beanDefinition.getBeanMethod()!=null){
                beanDefinition.getBeanMethod().setAccessible(true);
                if (beanDefinition.getBeanMethod().getParameterCount()==0) {
                    // 只执行无参数的 bean 方法, 有参数测bean方法涉及 自动装载 => 忽略
                    bean = beanDefinition.getBeanMethod().invoke(beanDefinition.getBeanConfig());
                }
            } else {
                Object beanClass = beanDefinition.getBeanClass();
                if(beanClass instanceof Class){
                    Class cls = (Class) beanClass;
                    bean = cls.newInstance();
                }
            }
            if(bean != null){
                singletonObjects.put(name,bean);
            }
        }
        singletonObjects.forEach((name,bean)->{
            System.out.printf("%s : %s \r\n", name, bean);
        });
    }

    // 搜索类文件
    public void scanClassFile(File dir, List<File> list){
        for (File f : dir.listFiles()) {
            if(f.isDirectory()){
                scanClassFile(f,list);
            } else if(f.isFile()){
                if (f.getName().endsWith(".class")) {
                    list.add(f);
                }
            } else {
               // 新建文件对象
//                File abcd = new File("abcd");// 既不是文件,也不是目录
//                abcd.mkdir(); //目录
//                abcd.createNewFile(); // 文件
            }
        }
    }

    // 实现 getBean 方法
    public <T> T getBean(Class<T> cls){
        // 查找该类型所有的beanDefinition, 确认哪个是primary
        List<Map.Entry<String, BeanDefinition>> list = beanDefinitionMap
                .entrySet()
                .stream()
                .filter(entry -> {
                    BeanDefinition beanDefinition = entry.getValue();
                    Object beanClass = beanDefinition.getBeanClass();
                    Class clazz = (Class) beanClass;
                    if (clazz.isAssignableFrom(cls)) {
                        return true;
                    }
                    return false;
                }).collect(Collectors.toList());
        if(list.isEmpty()){
            throw new RuntimeException("没有该bean:" + cls);
        } else if( list.size() == 1){
            Map.Entry<String, BeanDefinition> entry = list.get(0);
            BeanDefinition beanDefinition = entry.getValue();
            if (beanDefinition.isLazy()) {
                // TODO 请实现懒加载创建bean
            } else {
                String name = entry.getKey();
                return (T) singletonObjects.get(name);
            }
        } else {
            // 同一类型有多个bean, 找主bean
            List<Map.Entry<String, BeanDefinition>> primaryList = list.stream().filter(entry -> {
                if (entry.getValue().isPrimary()) {
                    return true;
                }
                return false;
            }).collect(Collectors.toList());
            if (primaryList.isEmpty()) {
                throw new RuntimeException("没有主bean: " + cls);
            } else if(primaryList.size() > 1){
                throw new RuntimeException("包含多个主bean: " + cls);
            } else {
                BeanDefinition beanDefinition = primaryList.get(0).getValue();
                if (beanDefinition.isLazy()) {
                    // TODO 请实现懒加载创建bean
                } else {
                    String name = primaryList.get(0).getKey();
                    return (T) singletonObjects.get(name);
                }
            }
        }
        throw new RuntimeException("没有该bean:" + cls);
    }

    public Object getBean(String id){
        Object bean = singletonObjects.get(id);
        if(bean != null){
            return bean;
        } else {
            // 懒加载
            BeanDefinition beanDefinition = beanDefinitionMap.get(id);
            if(beanDefinition==null){
                throw new RuntimeException("没有该bean:" + id);
            }
            // TODO 请实现懒加载创建bean
        }
        return bean;
    }
    public static void main(String[] args) throws Exception {
        MyAnnotationConfigApplicationContext cxt =
                new MyAnnotationConfigApplicationContext(AnnoConfig.class);

        UserMapper userMapper = cxt.getBean(UserMapper.class);
        System.out.println("-----userMapper = " + userMapper);

        Object userBiz = cxt.getBean("userBiz");
        System.out.println("------userBiz = " + userBiz);

        Object userCtrlAuto = cxt.getBean("userCtrlAuto");
        System.out.println("---------userCtrlAuto = " + userCtrlAuto);

        cxt.getBean("userCtrlAuto111111");

        // TODO 扩展 自动装载 autowired

    }

}

@Data
class BeanDefinition {

    private Object beanClass;   // bean 类型
    private Method beanMethod;  // bean方法
    private Object beanConfig;  // 配置类对象
    private boolean primary;
    private boolean lazy;

}

